use crate::config::device_config::DeviceConfig;
use crate::constant::c::{CLIENT_LOG_TARGET, DEVICE_LIST_FILE_NAME};
use crate::router::dto::device_entry::DeviceEntry;
use crate::{
    argument::RunArgument,
    config::{
        Config, channel_config::ChannelConfig, native_version_config::NativeVersionConfig,
        update_config::UpdateConfig,
    },
    constant::c::{CONFIG_FILE_NAME, UPDATE_SETTING_FILE_NAME},
    core::entity::native_version_pair::NativeVersionPair,
    router::channel_setting_router::rsp::GetChannelInfoResponse,
    util::Util,
};
use share::core::c::MAINLAND_API_DOMAIN;
use share::core::channel::Channel;
use std::path::PathBuf;
use std::{
    net::{IpAddr, Ipv4Addr, SocketAddr},
    sync::Arc,
};
use strum::IntoEnumIterator;
use tokio::{fs, signal, sync::oneshot};
use tracing::{Level, debug, error, info};
use tracing_appender::rolling::daily;
use tracing_subscriber::fmt::init;
use tracing_subscriber::fmt::time::OffsetTime;
use tracing_subscriber::fmt::writer::MakeWriterExt;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt};

pub mod argument;
pub mod config;
pub mod constant;
pub mod custom_filter;
pub mod error;
pub mod router;
pub mod util;

pub mod core;
pub mod nested_map;
#[cfg(test)]
pub mod test_util;

pub async fn generate_config(save: bool) {
    let update_setting_path = Util::app_dirs().config_dir().join(UPDATE_SETTING_FILE_NAME);

    let device_list_path = Util::app_dirs().config_dir().join(DEVICE_LIST_FILE_NAME);

    let config = Config::new(11850, update_setting_path, device_list_path);
    let s = toml::to_string_pretty(&config)
        .expect(&format!("Failed to serialize config, {:?}", &config));

    if save {
        let path = Util::app_dirs().config_dir().join(CONFIG_FILE_NAME);

        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .await
                .expect("Failed to create parent path");
        }

        fs::write(&path, s).await.expect(&format!(
            "Failed to write config to file, path: {:?}",
            &path
        ));

        println!("{}", &path.to_str().unwrap());

        return;
    }

    println!("{}", s);
}

pub async fn generate_update_setting(save: bool) {
    let mut channels = Vec::new();

    for c in Channel::iter() {
        let channel_setting = Util::get_channel_setting(c).await;
        channels.push(channel_setting);
    }

    let update_config = UpdateConfig { channels };
    let s = serde_json::to_string_pretty(&update_config).expect(&format!(
        "Failed to serialize update config, {:?}",
        &update_config
    ));

    if save {
        let path = Util::app_dirs().config_dir().join(UPDATE_SETTING_FILE_NAME);

        fs::write(&path, s).await.expect(&format!(
            "Failed to write update setting to file, path: {:?}",
            path
        ));

        println!("{}", &path.to_str().unwrap());

        return;
    }

    println!("{}", s);
}

pub async fn generate_device_list(save: bool) {
    let url = format!("https://{}/device/all", MAINLAND_API_DOMAIN);
    let json = reqwest::get(url.clone())
        .await
        .expect(&format!("Failed to get, url: {}", url.clone()))
        .text()
        .await
        .expect(&format!(
            "Failed to decode response body, url: {}",
            url.clone()
        ));

    let devices: Vec<DeviceEntry> = serde_json::from_str(&json).unwrap();
    let s = serde_json::to_string_pretty(&devices)
        .expect(&format!("Failed to serialize devices, {:?}", &devices));

    if save {
        let path = Util::app_dirs().config_dir().join(DEVICE_LIST_FILE_NAME);

        fs::write(&path, s).await.expect(&format!(
            "Failed to write device list to file, path: {:?}",
            path
        ));

        println!("{}", &path.to_str().unwrap());

        return;
    }

    println!("{}", s)
}

fn handle_signal(shutdown_tx: oneshot::Sender<()>) {
    tokio::spawn(async move {
        let ctrl_c = async {
            signal::ctrl_c()
                .await
                .expect("failed to install Ctrl+C handler");
        };

        #[cfg(unix)]
        let terminate = async {
            signal::unix::signal(signal::unix::SignalKind::terminate())
                .expect("failed to install signal handler")
                .recv()
                .await;
        };

        #[cfg(not(unix))]
        let terminate = std::future::pending::<()>();

        tokio::select! {
            _ = ctrl_c => info!("Receive ctrl_c, shutting down..."),
            _ = terminate => info!("Got terminate, shutting down..."),
        }

        let _ = shutdown_tx.send(());
    });
}

async fn get_devices_from_config(path: &PathBuf) -> Option<Vec<DeviceConfig>> {
    let device_list_json = tokio::fs::read_to_string(path).await;

    let Ok(json) = device_list_json else {
        return None;
    };

    let Ok(devices) = serde_json::from_str(&json) else {
        return None;
    };

    Some(devices)
}

fn init_log(log_dir: &PathBuf) {
    // 配置时间格式
    let time_fmt = time::format_description::parse(
        "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]",
    )
    .expect("Failed to parse time format");
    let time_offset = time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC);
    let timer = OffsetTime::new(time_offset, time_fmt);

    // 文件输出 - 无ANSI颜色
    let file_appender = daily(log_dir, "err.log")
        .with_max_level(Level::WARN)
        .or_else(daily(log_dir, "out.log"));

    let off = format!("{}=off", CLIENT_LOG_TARGET);
    let off_filter = EnvFilter::from_default_env().add_directive(
        off.parse()
            .expect(&format!("Failed to parse {off} to directive")),
    );
    let file_layer = fmt::layer()
        .with_writer(file_appender)
        .with_ansi(false) // 文件输出禁用ANSI颜色
        .with_timer(timer.clone())
        .with_target(true)
        .with_filter(off_filter);

    // 控制台输出 - 有ANSI颜色
    let std_appender = std::io::stderr
        .with_max_level(Level::WARN)
        .or_else(std::io::stdout);

    let console_layer = fmt::layer()
        .with_writer(std_appender)
        .with_ansi(true) // 控制台输出启用ANSI颜色
        .with_timer(timer.clone())
        .with_target(true);

    let cafe_layer = fmt::layer()
        .with_writer(daily(log_dir, "cafe.log"))
        .with_ansi(false)
        .with_timer(timer)
        .with_target(false)
        .with_level(false)
        .with_filter(EnvFilter::new(format!("{}=info", CLIENT_LOG_TARGET)));

    tracing_subscriber::registry()
        .with(EnvFilter::from_default_env())
        .with(file_layer)
        .with(console_layer)
        .with(cafe_layer)
        .init()
}

pub async fn run(args: RunArgument) {
    let log_dir = Util::app_dirs().data_dir().join("log");
    info!("Using log dir: {:?}", log_dir);
    init_log(&log_dir);

    let config = Config::with_argument(&args);
    info!(?config);

    let update_config_json = tokio::fs::read_to_string(config.update_setting_path())
        .await
        .expect("Failed to read update configuration file");
    let update_config = serde_json::from_str(&update_config_json)
        .expect("Failed to parse update configuration file");

    info!(?update_config);

    let devices = get_devices_from_config(config.device_list_path()).await;
    let devices = match devices {
        None => {
            error!("Failed to get device from config");
            vec![]
        }
        Some(d) => {
            info!(devices = ?d);
            d
        }
    };
    let filter = custom_filter::CustomFilter::new(update_config, devices);
    let router = router::get_all_router(Arc::new(filter));

    let ipv4 = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
    let socket = SocketAddr::new(ipv4, config.port());

    let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
    handle_signal(shutdown_tx);

    let (addr, server) = warp::serve(router).bind_with_graceful_shutdown(socket, async {
        shutdown_rx.await.ok();
        info!("ok")
    });

    info!("Listening on {}", addr);

    server.await;
}
